#include "../core/shape.h"

#include "../core/primitive.h"

namespace csupbr {

    bool Sphere::intersect(const Ray &ray, Intersection &isct) const {
        Ray oRay = (*w2o)(ray);
        float a = dot(oRay.direct);
        float b = 2.f * dot(oRay.origin, oRay.direct);
        float c = dot(oRay.origin) - radius * radius;
        float determination = b * b - 4.f * a * c;
        if (determination < 0)
            return false;
        float sq_det = std::sqrt(determination);
        float q = b < .0f ? -.5f * (b - sq_det) : -.5f * (b + sq_det);
        float t0 = q / a;
        float t1 = c / q;
        if (t0 > t1)
            std::swap(t0, t1);
        if (t0 > oRay.maxt || t1 < oRay.mint)
            return false;
        float tHit = t0;
        if (t0 < oRay.mint) {
            tHit = t1;
            if (t1 > oRay.maxt)
                return false;
        }
        auto pHit = oRay.to(tHit);
        if (pHit.x == 0 && pHit.y == 0)
            pHit.x = 1e-5f;
        Normal geoN = pHit;
        float phi = std::atan2(pHit.z, pHit.x);
        if (phi < 0.)
            phi += Pi2;
        float theta = std::acos(clamp(pHit.y / radius, -1.f, 1.f));
        Point2f uv = {phi / Pi2, theta / Pi};
        // fill Intersection
        isct.pHit = (*o2w)(pHit);
        isct.geoN = normalize((*o2w)(geoN));
        isct.shN = isct.geoN;
        isct.uv = uv;
        isct.geoCoord = CoordinateSystem(isct.geoN);
        isct.shCoord = CoordinateSystem(isct.shN);
        ray.maxt = tHit;
        return true;
    }
    bool Sphere::intersectP(const Ray &ray, bool blend_test) const {
        Ray oRay = (*w2o)(ray);
        float a = dot(oRay.direct);
        float b = 2.f * dot(oRay.origin, oRay.direct);
        float c = dot(oRay.origin) - 1.f;
        float determination = b * b - 4.f * a * c;
        if (determination < 0)
            return false;
        float sq_det = std::sqrt(determination);
        float q = b < .0f ? -.5f * (b - sq_det) : -.5f * (b + sq_det);
        float t0 = q / a;
        float t1 = c / q;
        if (t0 > t1)
            std::swap(t0, t1);
        if (t0 > oRay.maxt || t1 < oRay.mint ||
            (t0 < oRay.mint && t1 > oRay.maxt))
            return false;
        return true;
    }

    
    BBox3f Sphere::getBBox() const {
        return (*o2w)(BBox3f({-radius, -radius, -radius}, {radius, radius, radius}));
    }

}